/*-------------------------------------------------------------------------
 *
 * mcxt.c
 *	  POSTGRES 内存上下文管理代码。
 *
 * 此模块处理与所操作的特定类型上下文无关的上下文管理操作。
 * 它通过 MemoryContextMethods 结构中的函数指针调用特定于上下文类型的操作。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/mmgr/mcxt.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "storage/proc.h"
#include "storage/procarray.h"
#include "storage/procsignal.h"
#include "utils/fmgrprotos.h"
#include "utils/memdebug.h"
#include "utils/memutils.h"


/*****************************************************************************
 *	  GLOBAL MEMORY															 *
 *****************************************************************************/

/*
 * CurrentMemoryContext
 *		默认内存上下文用于分配。
 */
MemoryContext CurrentMemoryContext = NULL;

/*
 * 标准顶级上下文。有关每个上下文的目的的描述，请参见 src/backend/utils/mmgr/README
 */
MemoryContext TopMemoryContext = NULL;
MemoryContext ErrorContext = NULL;
MemoryContext PostmasterContext = NULL;
MemoryContext CacheMemoryContext = NULL;
MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext CurTransactionContext = NULL;

/* 这是对活动门户的内存上下文的瞬态链接： */
MemoryContext PortalContext = NULL;

static void fc_MemoryContextCallResetCallbacks(MemoryContext fc_context);
static void fc_MemoryContextStatsInternal(MemoryContext fc_context, int fc_level,
									   bool fc_print, int fc_max_children,
									   MemoryContextCounters *fc_totals,
									   bool fc_print_to_stderr);
static void fc_MemoryContextStatsPrint(MemoryContext fc_context, void *fc_passthru,
									const char *fc_stats_string,
									bool fc_print_to_stderr);

/*
 * 您不应该在关键节段内进行内存分配，因为
 * 内存不足错误会升级为恐慌。为了强制执行该
 * 规则，分配函数断言这一点。
 */
#define AssertNotInCriticalSection(context) \
	Assert(CritSectionCount == 0 || (context)->allowInCritSection)


/*****************************************************************************
 *	  EXPORTED ROUTINES														 *
 *****************************************************************************/


/*
 * MemoryContextInit
 *		启动内存上下文子系统。
 *
 * 在创建上下文或在上下文中分配内存之前必须调用此函数。这里初始化 TopMemoryContext 和 ErrorContext；其他上下文必须在之后创建。
 *
 * 在正常的多后端操作中，这在 postmaster 启动期间只调用一次，而不在单个后端启动时调用
 * （因为后端通过king自postmaster分叉继承了已经初始化的上下文子系统）。但在 EXEC_BACKEND
 * 构建中，每个进程必须为自己执行此操作。
 *
 * 在独立后端中，必须在后端启动期间调用此函数。
 */
void MemoryContextInit(void)
{
	AssertState(TopMemoryContext == NULL);

	/*
	 * 首先，初始化 TopMemoryContext，它是所有其他上下文的父级。
	 */
	TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL,
											 "TopMemoryContext",
											 ALLOCSET_DEFAULT_SIZES);

	/*
	 * 由于没有其他地方可以指向 CurrentMemoryContext，让它指向
	 * TopMemoryContext。调用者应该尽快更改这一点！
	 */
	CurrentMemoryContext = TopMemoryContext;

	/*
	 * 将 ErrorContext 初始化为一个 AllocSetContext，具有缓慢的增长速率 ---
	 * 我们并不真正期望在其中分配太多。更重要的是，
	 * 要求它在所有时候至少包含 8K。这是唯一一个
	 * 在上下文中保留内存是 *至关重要* 的情况 --- 我们希望确保
	 * 即使在其他地方用尽内存时，ErrorContext 仍然有某些内存！此外，允许在关键节段内进行 
	 * ErrorContext 中的分配。否则，恐慌将导致错误
	 * 报告代码中的断言失败，在打印出失败的真正原因之前。
	 *
	 * 这应该是此函数的最后一步，因为 elog.c 假定内存
	 * 管理在 ErrorContext 非空时工作。
	 */
	ErrorContext = AllocSetContextCreate(TopMemoryContext,
										 "ErrorContext",
										 8 * 1024,
										 8 * 1024,
										 8 * 1024);
	MemoryContextAllowInCriticalSection(ErrorContext, true);
}

/*
 * MemoryContextReset
 *		释放在上下文中分配的所有空间并删除其
 *		所有后代上下文（但不删除命名上下文本身）。
 */
void MemoryContextReset(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));

	/* 在没有子项的常见情况下保存一个函数调用 */
	if (fc_context->firstchild != NULL)
		MemoryContextDeleteChildren(fc_context);

	/* 如果自启动或上次重置以来没有 palloc，则保存一个函数调用 */
	if (!fc_context->isReset)
		MemoryContextResetOnly(fc_context);
}

/*
 * MemoryContextResetOnly
 *		释放在上下文中分配的所有空间。
 *		对上下文的后代上下文无法做任何事情。
 */
void MemoryContextResetOnly(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));

	/* 如果自启动或上次重置以来没有 palloc，则无需做任何事情 */
	if (!fc_context->isReset)
	{
		fc_MemoryContextCallResetCallbacks(fc_context);

		/*
		 * 如果 context->ident 指向上下文的内存，它将变成
		 * 一个悬空指针。我们可以通过将其设置为 NULL
		 * 来防止这种情况，但这会破坏有效的编码模式，保持
		 * ident 在其他地方，例如在父上下文中。另一种方法是使用
		 * MemoryContextContains()，但我们不要求 ident 字符串
		 * 在单独分配的块中，因此这存在误报的风险。所以
		 * 目前我们假设程序员做对了。
		 */

		fc_context->methods->reset(fc_context);
		fc_context->isReset = true;
		VALGRIND_DESTROY_MEMPOOL(context);
		VALGRIND_CREATE_MEMPOOL(context, 0, false);
	}
}

/*
 * MemoryContextResetChildren
 *		释放在上下文后代中分配的所有空间，
 *		但不删除上下文本身。命名上下文
 *		本身不会被触碰。
 */
void MemoryContextResetChildren(MemoryContext fc_context)
{
	MemoryContext fc_child;

	AssertArg(MemoryContextIsValid(fc_context));

	for (fc_child = fc_context->firstchild; fc_child != NULL; fc_child = fc_child->nextchild)
	{
		MemoryContextResetChildren(fc_child);
		MemoryContextResetOnly(fc_child);
	}
}


/*
 * MemoryContextDelete
 *		删除一个上下文及其子上下文，并释放其中分配的所有空间。
 *
 * 特定类型的删除例程会移除上下文的所有存储，但我们必须递归处理子上下文。
 * 如果上下文有父上下文，我们还必须将其从父上下文中解除链接。
 */
void MemoryContextDelete(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));
	/* 我们最好不要删除 TopMemoryContext ... */
	Assert(fc_context != TopMemoryContext);
	/* 也不是 CurrentMemoryContext */
	Assert(fc_context != CurrentMemoryContext);

	/* 在没有子项的常见情况下保存一个函数调用 */
	if (fc_context->firstchild != NULL)
		MemoryContextDeleteChildren(fc_context);

	/* 
	 * 这并不完全清楚，是在删除上下文之前还是之后这样做更好； 
	 * 但是在回调中发生错误可能会导致泄漏整个上下文（如果它不是根上下文），
	 * 所以我们在之前进行处理。
	 */
	fc_MemoryContextCallResetCallbacks(fc_context);

	/* 
	 * 我们在删除上下文之前将上下文从其父上下文中分离，
	 * 这样如果发生错误，我们就不会删除/损坏仍然附加到上下文树的上下文。
	 * 泄漏总比崩溃要好。
	 */
	MemoryContextSetParent(fc_context, NULL);

	/* 
	 * 同时重置上下文的标识指针，以防它指向上下文。
	 * 这只有在有人尝试获取（已经解除链接的）上下文的统计信息时才有意义，
	 * 这不太可能，但我们还是要安全起见。
	 */
	fc_context->ident = NULL;

	fc_context->methods->delete_context(fc_context);

	VALGRIND_DESTROY_MEMPOOL(context);
}

/* 
 * MemoryContextDeleteChildren
 *		删除指定上下文的所有后代，并释放其中分配的所有空间。
 *		不触碰指定上下文本身。
 */
void MemoryContextDeleteChildren(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));

	/* 
	 * MemoryContextDelete 将子上下文与我解除链接，因此只要有子上下文就循环迭代。
	 */
	while (fc_context->firstchild != NULL)
		MemoryContextDelete(fc_context->firstchild);
}

/* 
 * MemoryContextRegisterResetCallback
 *		注册一个在下一个上下文重置/删除之前调用的函数。
 *		此类回调将按注册的反向顺序调用。
 *
 * 调用者负责分配一个 MemoryContextCallback 结构来保存此回调请求的信息，
 * 并在结构中填写“func”和“arg”字段以显示要用什么参数调用什么函数。
 * 通常，回调结构应该在指定的上下文中分配，因为这样可以确保
 * 在不再需要时自动释放。
 *
 * 一旦注册后没有 API 来注销回调。如果你希望其不再执行任何操作，
 * 调整其“arg”指向的状态以指示这一点。
 */
void MemoryContextRegisterResetCallback(MemoryContext fc_context,
								   MemoryContextCallback *fc_cb)
{
	AssertArg(MemoryContextIsValid(fc_context));

	/* 推到头部，以便在较旧的注册之前调用。 */
	fc_cb->next = fc_context->reset_cbs;
	fc_context->reset_cbs = fc_cb;
	/* 将上下文标记为非重置（它可能已经是）。 */
	fc_context->isReset = false;
}

/* 
 * MemoryContextCallResetCallbacks
 *		内部函数，用于调用上下文的所有注册回调。
 */
static void fc_MemoryContextCallResetCallbacks(MemoryContext fc_context)
{
	MemoryContextCallback *fc_cb;

	/* 
	 * 我们在调用之前从列表中弹出每个回调。这样，如果回调内部发生错误，
	 * 我们就不会试图再调用它，鉴于我们之后可能会重置或删除上下文。
	 */
	while ((fc_cb = fc_context->reset_cbs) != NULL)
	{
		fc_context->reset_cbs = fc_cb->next;
		fc_cb->func(fc_cb->arg);
	}
}

/* 
 * MemoryContextSetIdentifier
 *		为内存上下文设置标识符字符串。
 *
 * 提供标识符可以帮助区分内存上下文统计转储中同类上下文。
 * 标识符字符串必须至少与它所对应的上下文一样长；
 * 通常它是在该上下文内部分配的，这样它在上下文删除时会自动消失。
 * 传递 id = NULL 以忘记任何旧标识符。
 */
void MemoryContextSetIdentifier(MemoryContext fc_context, const char *fc_id)
{
	AssertArg(MemoryContextIsValid(fc_context));
	fc_context->ident = fc_id;
}

/* 
 * MemoryContextSetParent
 *		将上下文更改为属于一个新的父上下文（或没有父上下文）。
 *
 * 我们将此作为 API 函数提供，因为在创建后更改上下文的生命周期有时是有用的。
 * 例如，一个上下文可能是在一个临时上下文下创建的，填充了数据，
 * 然后重新设置父上下文为 CacheMemoryContext，以使其长时间存在。
 * 这种方式避免了在其内容尚未完全设置之前发生故障时需要特别努力来清除上下文。
 *
 * 调用者往往假设此函数无法失败，因此不要在其中放置任何 elog(ERROR) 调用。
 *
 * 可能的调用者错误是将上下文重新设置为其自身下的父上下文，这会在上下文图中创建循环。
 * 我们在这里断言 context != new_parent，但检查多级循环似乎比值得付出更多努力。
 */
void MemoryContextSetParent(MemoryContext fc_context, MemoryContext fc_new_parent)
{
	AssertArg(MemoryContextIsValid(fc_context));
	AssertArg(fc_context != fc_new_parent);

	/* 如果它已经有了正确的父上下文，则快路径 */
	if (fc_new_parent == fc_context->parent)
		return;

	/* 从现有的父上下文中解除链接（如果有的话） */
	if (fc_context->parent)
	{
		MemoryContext fc_parent = fc_context->parent;

		if (fc_context->prevchild != NULL)
			fc_context->prevchild->nextchild = fc_context->nextchild;
		else
		{
			Assert(fc_parent->firstchild == fc_context);
			fc_parent->firstchild = fc_context->nextchild;
		}

		if (fc_context->nextchild != NULL)
			fc_context->nextchild->prevchild = fc_context->prevchild;
	}

	/* 然后重新链接 */
	if (fc_new_parent)
	{
		AssertArg(MemoryContextIsValid(fc_new_parent));
		fc_context->parent = fc_new_parent;
		fc_context->prevchild = NULL;
		fc_context->nextchild = fc_new_parent->firstchild;
		if (fc_new_parent->firstchild != NULL)
			fc_new_parent->firstchild->prevchild = fc_context;
		fc_new_parent->firstchild = fc_context;
	}
	else
	{
		fc_context->parent = NULL;
		fc_context->prevchild = NULL;
		fc_context->nextchild = NULL;
	}
}

/* 
 * MemoryContextAllowInCriticalSection
 *		允许/不允许在此内存上下文中进行分配，处于关键部分内。
 *
 * 通常，在关键部分内不允许进行内存分配，
 * 因为失败会导致 PANIC。对此有一些例外，
 * 比如与调试代码相关的分配，这些代码在生产中不应启用。
 * 可以使用此函数来豁免特定的内存上下文不受 palloc() 中的断言限制。
 */
void MemoryContextAllowInCriticalSection(MemoryContext fc_context, bool fc_allow)
{
	AssertArg(MemoryContextIsValid(fc_context));

	fc_context->allowInCritSection = fc_allow;
}

/* 
 * GetMemoryChunkSpace
 *		给定一个当前分配的块，确定其占用的总空间（包括所有内存分配开销）。
 *
 * 这对于测量一组分配块所占用的总空间很有用。
 */
Size GetMemoryChunkSpace(void *fc_pointer)
{
	MemoryContext fc_context = GetMemoryChunkContext(fc_pointer);

	return fc_context->methods->get_chunk_space(fc_context, fc_pointer);
}

/* 
 * MemoryContextGetParent
 *		获取指定上下文的父上下文（如果有的话）。
 */
MemoryContext MemoryContextGetParent(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));

	return fc_context->parent;
}

/* 
 * MemoryContextIsEmpty
 *		内存上下文是否没有任何分配的空间？
 */
bool MemoryContextIsEmpty(MemoryContext fc_context)
{
	AssertArg(MemoryContextIsValid(fc_context));

	/* 
	 * 目前，如果内存上下文有子上下文，我们认为它非空；
	 * 也许以后应该更改这一点。
	 */
	if (fc_context->firstchild != NULL)
		return false;
	/* 否则使用特定类型的查询 */
	return fc_context->methods->is_empty(fc_context);
}

/*
 * 查找分配给此内存上下文块的内存。如果 recurse 为真，还包括子项。
 */
Size MemoryContextMemAllocated(MemoryContext fc_context, bool fc_recurse)
{
	Size		fc_total = fc_context->mem_allocated;

	AssertArg(MemoryContextIsValid(fc_context));

	if (fc_recurse)
	{
		MemoryContext fc_child;

		for (fc_child = fc_context->firstchild;
			 fc_child != NULL;
			 fc_child = fc_child->nextchild)
			fc_total += MemoryContextMemAllocated(fc_child, true);
	}

	return fc_total;
}

/*
 * MemoryContextStats
 *		打印关于命名上下文及其所有后代的统计信息。
 *
 * 这只是一个调试工具，所以它并不是很华丽。不过，我们确实尽量总结一下，否则输出可能会很长。
 * 统计信息发送到 stderr。
 */
void MemoryContextStats(MemoryContext fc_context)
{
	/* 对子项数量的硬性限制通常足够好 */
	MemoryContextStatsDetail(fc_context, 100, true);
}

/*
 * MemoryContextStatsDetail
 *
 * 如果您想要改变显示的子上下文数量，可以使用的入口点。
 *
 * 如果 print_to_stderr 为真，则使用 fprintf(stderr) 打印关于内存上下文的统计信息，否则使用 ereport()。
 */
void MemoryContextStatsDetail(MemoryContext fc_context, int fc_max_children,
						 bool fc_print_to_stderr)
{
	MemoryContextCounters fc_grand_totals;

	memset(&fc_grand_totals, 0, sizeof(fc_grand_totals));

	fc_MemoryContextStatsInternal(fc_context, 0, true, fc_max_children, &fc_grand_totals, fc_print_to_stderr);

	if (fc_print_to_stderr)
		fprintf(stderr,
				"Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used\n",
				fc_grand_totals.totalspace, fc_grand_totals.nblocks,
				fc_grand_totals.freespace, fc_grand_totals.freechunks,
				fc_grand_totals.totalspace - fc_grand_totals.freespace);
	else

		/*
		 * 使用 LOG_SERVER_ONLY 来防止内存上下文被发送到连接的客户端。
		 *
		 * 我们不将后端中的所有内存上下文信息缓冲到 StringInfo 中并作为一条消息记录。
		 * 否则可能会导致缓冲区巨大增加，导致 OOM 错误，因为后端中可能有大量内存上下文。
		 * 相反，我们每个内存上下文记录一条消息。
		 */
		ereport(LOG_SERVER_ONLY,
				(errhidestmt(true),
				 errhidecontext(true),
				 errmsg_internal("Grand total: %zu bytes in %zu blocks; %zu free (%zu chunks); %zu used",
								 fc_grand_totals.totalspace, fc_grand_totals.nblocks,
								 fc_grand_totals.freespace, fc_grand_totals.freechunks,
								 fc_grand_totals.totalspace - fc_grand_totals.freespace)));
}

/*
 * MemoryContextStatsInternal
 *		MemoryContextStats 的一个递归级别
 *
 * 如果 print 为真，则打印此上下文，但无论如何将计数累积到 *totals（如果给定）。
 */
static void fc_MemoryContextStatsInternal(MemoryContext fc_context, int fc_level,
						   bool fc_print, int fc_max_children,
						   MemoryContextCounters *fc_totals,
						   bool fc_print_to_stderr)
{
	MemoryContextCounters fc_local_totals;
	MemoryContext fc_child;
	int			fc_ichild;

	AssertArg(MemoryContextIsValid(fc_context));

	/* 检查上下文本身 */
	fc_context->methods->stats(fc_context,
							fc_print ? fc_MemoryContextStatsPrint : NULL,
							(void *) &fc_level,
							fc_totals, fc_print_to_stderr);

	/*
	 * 检查子项。如果它们的数量超过 max_children，我们将不显式打印其余项，而只是总结它们。
	 */
	memset(&fc_local_totals, 0, sizeof(fc_local_totals));

	for (fc_child = fc_context->firstchild, fc_ichild = 0;
		 fc_child != NULL;
		 fc_child = fc_child->nextchild, fc_ichild++)
	{
		if (fc_ichild < fc_max_children)
			fc_MemoryContextStatsInternal(fc_child, fc_level + 1,
									   fc_print, fc_max_children,
									   fc_totals,
									   fc_print_to_stderr);
		else
			fc_MemoryContextStatsInternal(fc_child, fc_level + 1,
									   false, fc_max_children,
									   &fc_local_totals,
									   fc_print_to_stderr);
	}

	/* 处理过多的子项 */
	if (fc_ichild > fc_max_children)
	{
		if (fc_print)
		{
			if (fc_print_to_stderr)
			{
				int			fc_i;

				for (fc_i = 0; fc_i <= fc_level; fc_i++)
					fprintf(stderr, "  ");
				fprintf(stderr,
						"%d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used\n",
						fc_ichild - fc_max_children,
						fc_local_totals.totalspace,
						fc_local_totals.nblocks,
						fc_local_totals.freespace,
						fc_local_totals.freechunks,
						fc_local_totals.totalspace - fc_local_totals.freespace);
			}
			else
				ereport(LOG_SERVER_ONLY,
						(errhidestmt(true),
						 errhidecontext(true),
						 errmsg_internal("level: %d; %d more child contexts containing %zu total in %zu blocks; %zu free (%zu chunks); %zu used",
										 fc_level,
										 fc_ichild - fc_max_children,
										 fc_local_totals.totalspace,
										 fc_local_totals.nblocks,
										 fc_local_totals.freespace,
										 fc_local_totals.freechunks,
										 fc_local_totals.totalspace - fc_local_totals.freespace)));
		}

		if (fc_totals)
		{
			fc_totals->nblocks += fc_local_totals.nblocks;
			fc_totals->freechunks += fc_local_totals.freechunks;
			fc_totals->totalspace += fc_local_totals.totalspace;
			fc_totals->freespace += fc_local_totals.freespace;
		}
	}
}

/*
 * MemoryContextStatsPrint
 *		打印回调，由 MemoryContextStatsInternal 使用
 *
 * 现在，passthru 指针只指向 "int level"; 以后我们可能会
 * 让它更复杂一点。
 */
static void fc_MemoryContextStatsPrint(MemoryContext fc_context, void *fc_passthru,
						const char *fc_stats_string,
						bool fc_print_to_stderr)
{
	int			fc_level = *(int *) fc_passthru;
	const char *fc_name = fc_context->name;
	const char *fc_ident = fc_context->ident;
	char		fc_truncated_ident[110];
	int			fc_i;

	/*
	 * 更好的是仅用哈希表名称标记 dynahash 上下文。
	 * 这些名称已经足够唯一，因此 "dynahash" 部分并不是
	 * 非常有用，这种方式也与 v11 之前的做法更一致。
	 */
	if (fc_ident && strcmp(fc_name, "dynahash") == 0)
	{
		fc_name = fc_ident;
		fc_ident = NULL;
	}

	fc_truncated_ident[0] = '\0';

	if (fc_ident)
	{
		/*
		 * 一些上下文可能有非常长的标识符 (例如，SQL 查询)。
		 * 随意截断到 100 字节，但要小心不要打断
		 * 多字节字符。同时，将 ASCII 控制字符，如
		 * 换行符，替换为空格。
		 */
		int			fc_idlen = strlen(fc_ident);
		bool		fc_truncated = false;

		strcpy(fc_truncated_ident, ": ");
		fc_i = strlen(fc_truncated_ident);

		if (fc_idlen > 100)
		{
			fc_idlen = pg_mbcliplen(fc_ident, fc_idlen, 100);
			fc_truncated = true;
		}

		while (fc_idlen-- > 0)
		{
			unsigned char fc_c = *fc_ident++;

			if (fc_c < ' ')
				fc_c = ' ';
			fc_truncated_ident[fc_i++] = fc_c;
		}
		fc_truncated_ident[fc_i] = '\0';

		if (fc_truncated)
			strcat(fc_truncated_ident, "...");
	}

	if (fc_print_to_stderr)
	{
		for (fc_i = 0; fc_i < fc_level; fc_i++)
			fprintf(stderr, "  ");
		fprintf(stderr, "%s: %s%s\n", fc_name, fc_stats_string, fc_truncated_ident);
	}
	else
		ereport(LOG_SERVER_ONLY,
				(errhidestmt(true),
				 errhidecontext(true),
				 errmsg_internal("level: %d; %s: %s%s",
								 fc_level, fc_name, fc_stats_string, fc_truncated_ident)));
}

/*
 * MemoryContextCheck
 *		检查命名上下文中的所有块。
 *
 * 这只是一个调试工具，因此它并不复杂。
 */
#ifdef MEMORY_CONTEXT_CHECKING
void MemoryContextCheck(MemoryContext fc_context)
{
	MemoryContext fc_child;

	AssertArg(MemoryContextIsValid(fc_context));

	fc_context->methods->check(fc_context);
	for (fc_child = fc_context->firstchild; fc_child != NULL; fc_child = fc_child->nextchild)
		MemoryContextCheck(fc_child);
}
#endif

/*
 * MemoryContextContains
 *		检测一个已分配的内存块是否属于给定的
 *		上下文。
 *
 * 注意：只要 'pointer' 指向某个上下文中分配的
 * 一块内存，此测试就是可靠的。如果 'pointer' 指向
 * 以其他方式获得的内存，则存在误报的可能性，
 * 因为在它之前的位可能恰好看起来像一个有效的块头。
 */
bool MemoryContextContains(MemoryContext fc_context, void *fc_pointer)
{
	MemoryContext fc_ptr_context;

	/*
	 * 注意：这里不能使用 GetMemoryChunkContext() - 
	 * 这会执行一些在这里不被接受的断言，
	 * 因为我们可能会接收到未由任何内存上下文分配的内存。
	 *
	 * 尽量检测传给我们的无效指针，尽管我们可能做得很糟糕。
	 * 可以推测，一个不是 MAXALIGNED 的指针不指向一个
	 * 已分配的块。
	 */
	if (fc_pointer == NULL || fc_pointer != (void *) MAXALIGN(fc_pointer))
		return false;

	/*
	 * 好吧，查看上下文可能是安全的。
	 */
	fc_ptr_context = *(MemoryContext *) (((char *) fc_pointer) - sizeof(void *));

	return fc_ptr_context == fc_context;
}

/*
 * MemoryContextCreate
 *		上下文创建的与上下文类型无关的部分。
 *
 * 这只打算由特定上下文类型的上下文创建例程调用，
 * 而不是由未洗礼的大众调用。
 *
 * 内存上下文创建过程如下：
 *	1.  特定上下文类型的例程进行一些初始空间分配，
 *		包括足够的空间用于上下文头。如果失败，
 *		它可以 ereport() 而不会造成损害。
 *	2.	特定上下文类型的例程设置所有类型特定的头部字段
 *		（超出 MemoryContextData 正常部分），以及任何
 *		其他管理字段，以便拥有一个完全有效的上下文。
 *		通常，这一步的失败是不可能的，但是如果可能，
 *		初始空间分配应在 ereport 之前释放。
 *	3.	特定上下文类型的例程调用 MemoryContextCreate()
 *		来填充通用头部字段，并将上下文链接到上下文树中。
 *	4.  我们返回到特定上下文类型的例程，它完成
 *		类型特定的初始化。这个例程现在可以执行可能失败的操作
 *		（比如分配更多内存），只要确保节点
 *		保持在 delete 能处理的状态。
 *
 * node: 还未初始化的上下文头部节点的通用部分。
 * tag: NodeTag 代码标识内存上下文类型。
 * methods: 特定上下文类型的方法（通常静态分配）。
 * parent: 父上下文，如果这是顶级上下文则为 NULL。
 * name: 上下文名称（必须静态分配）。
 *
 * 上下文例程通常假设 MemoryContextCreate 不能失败，
 * 所以这可以包含 Assert 但不能包含 elog/ereport。
 */
void MemoryContextCreate(MemoryContext fc_node,
					NodeTag fc_tag,
					const MemoryContextMethods *fc_methods,
					MemoryContext fc_parent,
					const char *fc_name)
{
	/* 在临界区内不允许创建新的内存上下文 */
	Assert(CritSectionCount == 0);

	/* 初始化内存上下文头部的所有标准字段 */
	fc_node->type = fc_tag;
	fc_node->isReset = true;
	fc_node->methods = fc_methods;
	fc_node->parent = fc_parent;
	fc_node->firstchild = NULL;
	fc_node->mem_allocated = 0;
	fc_node->prevchild = NULL;
	fc_node->name = fc_name;
	fc_node->ident = NULL;
	fc_node->reset_cbs = NULL;

	/* 可以将节点链接到上下文树中 */
	if (fc_parent)
	{
		fc_node->nextchild = fc_parent->firstchild;
		if (fc_parent->firstchild != NULL)
			fc_parent->firstchild->prevchild = fc_node;
		fc_parent->firstchild = fc_node;
		/* 从父级继承 allowInCritSection 标志 */
		fc_node->allowInCritSection = fc_parent->allowInCritSection;
	}
	else
	{
		fc_node->nextchild = NULL;
		fc_node->allowInCritSection = false;
	}

	VALGRIND_CREATE_MEMPOOL(node, 0, false);
}

/*
 * MemoryContextAlloc
 *		在指定上下文中分配空间。
 *
 * 这可以变成一个宏，但我们必须将 nodes/memnodes.h 导入
 * postgres.h，这似乎是个坏主意。
 */
void * MemoryContextAlloc(MemoryContext fc_context, Size fc_size)
{
	void	   *fc_ret;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);

		/*
		 * 在此模块的这里和其他地方，我们在用户可见的错误信息中显示目标上下文的
		 * "name"，但不显示其 "ident"（如果有的话）。 "ident" 字符串可能包含安全敏感数据，
		 * 例如 SQL 命令中的值。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	return fc_ret;
}

/*
 * MemoryContextAllocZero
 *		类似于 MemoryContextAlloc，清空分配的内存
 *
 *	我们可以简单地调用 MemoryContextAlloc 然后清空内存，
 * 但这是一个非常常见的组合，因此我们提供了这个组合操作。
 */
void * MemoryContextAllocZero(MemoryContext fc_context, Size fc_size)
{
	void	   *fc_ret;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	MemSetAligned(fc_ret, 0, fc_size);

	return fc_ret;
}

/*
 * MemoryContextAllocZeroAligned
 *		MemoryContextAllocZero，其中长度适合 MemSetLoop
 *
 *	这看似过于专业化，但并非如此，因为 newNode()
 * 在编译时通常被调用时大小是常量。
 */
void * MemoryContextAllocZeroAligned(MemoryContext fc_context, Size fc_size)
{
	void	   *fc_ret;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	MemSetLoop(fc_ret, 0, fc_size);

	return fc_ret;
}

/*
 * MemoryContextAllocExtended
 *		在指定上下文中使用给定标志分配空间。
 */
void * MemoryContextAllocExtended(MemoryContext fc_context, Size fc_size, int fc_flags)
{
	void	   *fc_ret;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (((fc_flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(fc_size)) ||
		((fc_flags & MCXT_ALLOC_HUGE) == 0 && !AllocSizeIsValid(fc_size)))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		if ((fc_flags & MCXT_ALLOC_NO_OOM) == 0)
		{
			MemoryContextStats(TopMemoryContext);
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory"),
					 errdetail("Failed on request of size %zu in memory context \"%s\".",
							   fc_size, fc_context->name)));
		}
		return NULL;
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	if ((fc_flags & MCXT_ALLOC_ZERO) != 0)
		MemSetAligned(fc_ret, 0, fc_size);

	return fc_ret;
}


/*
 * HandleLogMemoryContextInterrupt
 *		处理接收指示内存上下文日志记录的中断。
 *
 * 所有实际工作都被推迟到 ProcessLogMemoryContextInterrupt()，
 * 因为我们不能安全地在信号处理程序中发出日志消息。
 */
void HandleLogMemoryContextInterrupt(void)
{
	InterruptPending = true;
	LogMemoryContextPending = true;
	/* latch 将由 procsignal_sigusr1_handler 设置 */
}

/*
 * ProcessLogMemoryContextInterrupt
 * 		执行此后端进程的内存上下文日志记录。
 *
 * 任何参与 ProcSignal 信号的后端必须安排在看到 LogMemoryContextPending
 * 设置时调用此函数。它是从 CHECK_FOR_INTERRUPTS() 调用的，这就足够了，因为
 * 内存上下文日志记录的目标进程是一个后端。
 */
void ProcessLogMemoryContextInterrupt(void)
{
	LogMemoryContextPending = false;

	/*
	 * 使用 LOG_SERVER_ONLY 防止此消息发送到
	 * 连接的客户端。
	 */
	ereport(LOG_SERVER_ONLY,
			(errhidestmt(true),
			 errhidecontext(true),
			 errmsg("logging memory contexts of PID %d", MyProcPid)));

	/*
	 * 当后端进程消耗大量内存时，记录其所有内存上下文可能会超出
	 * 可用磁盘空间。为防止这种情况，我们将每个父上下文要记录的子上下文数量
	 * 限制为 100。
	 *
	 * 与 MemoryContextStats() 一样，我们假设在实际案例中，转储变长通常是
	 * 在同一父上下文下大量兄弟之间；而查看超过 100 个兄弟的详细信息带来的
	 * 附加调试价值不会太大。
	 */
	MemoryContextStatsDetail(TopMemoryContext, 100, false);
}

void * palloc(Size fc_size)
{
	/* 复制 MemoryContextAlloc 以避免增加开销 */
	void	   *fc_ret;
	MemoryContext fc_context = CurrentMemoryContext;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	return fc_ret;
}

void * palloc0(Size fc_size)
{
	/* 复制 MemoryContextAllocZero 以避免增加开销 */
	void	   *fc_ret;
	MemoryContext fc_context = CurrentMemoryContext;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	MemSetAligned(fc_ret, 0, fc_size);

	return fc_ret;
}

void * palloc_extended(Size fc_size, int fc_flags)
{
	/* 复制 MemoryContextAllocExtended 以避免增加开销 */
	void	   *fc_ret;
	MemoryContext fc_context = CurrentMemoryContext;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (((fc_flags & MCXT_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(fc_size)) ||
		((fc_flags & MCXT_ALLOC_HUGE) == 0 && !AllocSizeIsValid(fc_size)))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		if ((fc_flags & MCXT_ALLOC_NO_OOM) == 0)
		{
			MemoryContextStats(TopMemoryContext);
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory"),
					 errdetail("Failed on request of size %zu in memory context \"%s\".",
							   fc_size, fc_context->name)));
		}
		return NULL;
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	if ((fc_flags & MCXT_ALLOC_ZERO) != 0)
		MemSetAligned(fc_ret, 0, fc_size);

	return fc_ret;
}

/*
 * pfree
 *		释放已分配的块。
 */
void pfree(void *fc_pointer)
{
	MemoryContext fc_context = GetMemoryChunkContext(fc_pointer);

	fc_context->methods->free_p(fc_context, fc_pointer);
	VALGRIND_MEMPOOL_FREE(context, pointer);
}

/*
 * repalloc
 *		调整先前分配块的大小。
 */
void * repalloc(void *fc_pointer, Size fc_size)
{
	MemoryContext fc_context = GetMemoryChunkContext(fc_pointer);
	void	   *fc_ret;

	if (!AllocSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	AssertNotInCriticalSection(fc_context);

	/* isReset 必须已经为 false */
	Assert(!fc_context->isReset);

	fc_ret = fc_context->methods->realloc(fc_context, fc_pointer, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);

	return fc_ret;
}

/*
 * MemoryContextAllocHuge
 *		在指定上下文中分配（可能是扩展的）空间。
 *
 * 请参阅 MaxAllocHugeSize 中的注释中的考虑事项。
 */
void * MemoryContextAllocHuge(MemoryContext fc_context, Size fc_size)
{
	void	   *fc_ret;

	AssertArg(MemoryContextIsValid(fc_context));
	AssertNotInCriticalSection(fc_context);

	if (!AllocHugeSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	fc_context->isReset = false;

	fc_ret = fc_context->methods->alloc(fc_context, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_ALLOC(context, ret, size);

	return fc_ret;
}

/*
 * repalloc_huge
 *		调整先前分配块的大小，允许大值。
 *		之前的分配不必是“巨大”的。
 */
void * repalloc_huge(void *fc_pointer, Size fc_size)
{
	MemoryContext fc_context = GetMemoryChunkContext(fc_pointer);
	void	   *fc_ret;

	if (!AllocHugeSizeIsValid(fc_size))
		elog(ERROR, "invalid memory alloc request size %zu", fc_size);

	AssertNotInCriticalSection(fc_context);

	/* isReset 必须已经为 false */
	Assert(!fc_context->isReset);

	fc_ret = fc_context->methods->realloc(fc_context, fc_pointer, fc_size);
	if (unlikely(fc_ret == NULL))
	{
		MemoryContextStats(TopMemoryContext);
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed on request of size %zu in memory context \"%s\".",
						   fc_size, fc_context->name)));
	}

	VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);

	return fc_ret;
}

/*
 * MemoryContextStrdup
 *		像 strdup()，但从指定上下文分配
 */
char * MemoryContextStrdup(MemoryContext fc_context, const char *fc_string)
{
	char	   *fc_nstr;
	Size		fc_len = strlen(fc_string) + 1;

	fc_nstr = (char *) MemoryContextAlloc(fc_context, fc_len);

	memcpy(fc_nstr, fc_string, fc_len);

	return fc_nstr;
}

char * pstrdup(const char *fc_in)
{
	return MemoryContextStrdup(CurrentMemoryContext, fc_in);
}

/*
 * pnstrdup
 *		类似于 pstrdup()，但将空字节附加到一个
 *		不一定以空结尾的输入字符串。
 */
char * pnstrdup(const char *fc_in, Size fc_len)
{
	char	   *fc_out;

	fc_len = strnlen(fc_in, fc_len);

	fc_out = palloc(fc_len + 1);
	memcpy(fc_out, fc_in, fc_len);
	fc_out[fc_len] = '\0';

	return fc_out;
}

/*
 * 复制字符串并去除所有尾部换行符。
 */
char * pchomp(const char *fc_in)
{
	size_t		fc_n;

	fc_n = strlen(fc_in);
	while (fc_n > 0 && fc_in[fc_n - 1] == '\n')
		fc_n--;
	return pnstrdup(fc_in, fc_n);
}
